package SVN::TeamTools::DepFinder;

use warnings;
use strict;
use Carp;
use Error qw(:try);

use SVN::TeamTools::Store::Config;
use SVN::TeamTools::Store::SvnIndex;
use SVN::TeamTools::Store::DepIndex;

use SVN::Look;
use Getopt::Long;
use List::MoreUtils 'any';

use Data::Dumper;

use Forks::Super;
use Math::Round;

my $conf;
my $logger;
BEGIN { $conf = SVN::TeamTools::Store::Config->new(); $logger = $conf->{logger}; }

### Constants
my $createindex = 0;
my $maxworkers	= 8;
my @ignore;
my $svnindex;
my $depindex;

my %svnmod;
my %depmod;
my %svndoc;
my %depdoc;

my $doccnt=0;
my $lnkcnt=0;

sub addResult {
	my %args	= @_;
	my $pmod	= $args{module};
	my $pres	= $args{res};
	while (my $rel = $pres->next()) {
		if ( $rel->{module} !~ /^$pmod$/i) {
			$depindex->addDep(module=>$pmod, path=>$rel->{path});
			$doccnt++;
			## Expoding links
			my $links = $svnindex->execANDQuery (
				queries=>[ $svnindex->getTermQuery (field=>"cfpath", term=>$rel->{path}) ]
			);
##			my $links=$svnindex->getPostings(field=>"cfpath", term=>$rel->{path});
			while (my $link = $links->next()) {
				$lnkcnt++;
				$depindex->addDep(module=>$pmod, path=>$link->{path});
			}
		}
	}
}

sub modWorkers {
	my $process	= shift;
	my @worklist	= @_;

	my $cnt=0;
	$logger->info( "Worker $process will analyse ".scalar(@worklist). " keywords");
	foreach my $keyword (@worklist) {

		$logger->debug ("Analyzing added module " . uc($keyword));
		my $res = $svnindex->getPostings(field=>"content", term=>$keyword);
	
		while (my $rel = $res->next()) {
			if ( $rel->{module} !~ /^$keyword$/i) {
				print "#".uc($keyword).",".$rel->{path}."#\n";
				$doccnt++;
			## Expoding links
				my $links = $svnindex->getPostings(field=>"cfpath", term=>$rel->{path});
				while (my $link = $links->next()) {
					$lnkcnt++;
					print "#".uc($keyword).",".$link->{path}."#\n";
				}
			}
		}
		$cnt++;

		if (! ($cnt % 50)) {
			$logger->info ("Worker $process analyzed $cnt modules (".round(100*$cnt/scalar(@worklist))."%)");
			$logger->info ("All workers added $doccnt docs and $lnkcnt links");
		}
	}
	print "___END___\n";
}

sub run {
	@ignore		= @{$conf->{config}->{depindex}->{exclude}};
	push (@ignore, ('abstract','all','alter','array','assert','begin','between','body','boolean','break','by','byte','case','catch','char','class','copy','comment','command','const','continue','create','constant','constants','css','date','declare','default','delete','distinct','do','double','else','end','enum','exclusive','exists','extends','fetch','final','finally','float','for','from','function','goto','grant','having','identified','if','implements','import','index','instanceof','int','integer','interface','intersect','into','long','main','minus','native','new','order','package','prior','private','procedure','protected','public','record','replace','return','struct','select','short','start','static','stop','strictfp','super','switch','synchronized','table','this','throw','throws','transient','truncate','try','union','update','user','users','values','view','views','void','volatile','where','while'));


	$svnindex 	= SVN::TeamTools::Store::SvnIndex->new (mode=>"r", create=>0);
	$depindex 	= SVN::TeamTools::Store::DepIndex->new (mode=>"rw", create=>0);

	my $deprev = $depindex->getIndexRev() + 1;
	my $svnrev = $svnindex->getIndexRev();

	$logger->info ("Starting dependency analysis, from rev $deprev to $svnrev");
	$logger->info ("... Excluding: " . join (",", @ignore));
	
	### Getting modules from the index
	my $p_svnmod = fork {
			share=> [ \%svnmod , \@ignore ], untaint => 1,
			sub => sub {
				my $p_svnindex 	= SVN::TeamTools::Store::SvnIndex->new (mode=>"r", create=>0);
				%svnmod = $p_svnindex->getUniques (field => "module");

				### Removing ignored modules
				foreach my $m (@ignore) {
					delete $svnmod{uc($m)};
				}

				undef $p_svnindex;
			}
		};
	$logger->info ("Getting modules from searchindex started with pid: $p_svnmod");
	
	### Getting analyzed modules
	my $p_depmod = fork {
			share=>[ \%depmod ], untaint => 1,
			sub => sub {
				my $p_depindex 	= SVN::TeamTools::Store::DepIndex->new (mode=>"r", create=>0);
				%depmod = $p_depindex->getUniques (field => "module");
				undef $p_depindex;
			}
		};
	$logger->info ("Getting modules from depindex started with pid: $p_depmod");
	
	### Getting files from the index
	my $p_svndoc = fork {
			share=>[ \%svndoc ], untaint => 1,
			sub => sub {
				my $p_svnindex 	= SVN::TeamTools::Store::SvnIndex->new (mode=>"r", create=>0);
				%svndoc = $p_svnindex->getUniques (field => "path");
				undef $p_svnindex;
			}
		};
	$logger->info ("Getting documents from searchindex started with pid: $p_svndoc");
	
	### Getting analyzed files
	my $p_depdoc = fork {
			share=>[ \%depdoc ], untaint => 1,
			sub => sub {
				my $p_depindex 	= SVN::TeamTools::Store::DepIndex->new (mode=>"r", create=>0);
				%depdoc = $p_depindex->getUniques (field => "path");
				undef $p_depindex;
			}
		};
	$logger->info ("Getting documents from depindex started with pid: $p_depdoc");

	waitpid $p_svnmod,0;
	waitpid $p_depmod,0;
	waitpid $p_svndoc,0;
	waitpid $p_depdoc,0;

	$logger->info ("Loaded " . scalar(keys(%svnmod)) . " modules from search index");
	$logger->info ("Loaded " . scalar(keys(%depmod)) . " modules from dependency index");
	$logger->info ("Loaded " . scalar(keys(%svndoc)) . " documents from search index");
	$logger->info ("Loaded " . scalar(keys(%depdoc)) . " documents from dependency index");
		
	### Crossreferencing module lists
	$logger->info ("Crossreferencing modules");
	foreach my $m (keys %svnmod) {
		if (defined $depmod{$m}) {
			delete $depmod{$m};
			delete $svnmod{$m};
		}
	}
	my @currmod;
	foreach my $m (keys %depmod) {
		if (defined $svnmod{$m}) {
			delete $depmod{$m};
			delete $svnmod{$m};
			push (@currmod, $m);
		}
	}
	
	foreach my $m (keys %depdoc) {
		if (defined $svndoc{$m}) {
			delete $depdoc{$m};
			delete $svndoc{$m};
		}
	}
	
	### Prepare filter
	my $docfilter = $svnindex->getTermFilter(field => "type",term => "doc");
	
	### Analyzing added files
	$logger->info ("Analyzing added files in " . ($svnrev - $deprev) . " revisions");
	for (my $i=$deprev; $i<=$svnrev; $i++) {
		$logger->debug ("Analyzing added files in revision $i");
		my $revfilter = $svnindex->getTermFilter(field => "rev",term => $i);
	
		foreach my $mod (@currmod) {
			my $res = $svnindex->execANDQuery (queries=>[
				$revfilter, $docfilter,
				$svnindex->getQuery (field=>"content", term=>$mod)
			]);
	
			addResult (module => $mod, res => $res);
		}
	}
	
	### Analyzing added modules
	$logger->info ("Analyzing " . scalar(keys %svnmod) . " added modules");
	my $lex = $svnindex->getLexicon(field=>'content');
	$logger->info ("... going through " . $lex->length() . " keywords");

	my @worklist = ();
	for my $i ( 1 .. $maxworkers ) {
		push (@worklist, [$i]);
	}
	my $i=0;
	while( my $keyword = $lex->next()) {
		if (defined $svnmod{uc($keyword)}) {
			push (@{$worklist[$i++]}, $keyword);
			$i=$i % $maxworkers;
		}
	}

	my @workers;
	for $i ( 0 .. $maxworkers-1 ) {
		my $wpid = fork { args=>$worklist[$i], child_fh=>"all pipe", sub=>\&modWorkers };
		$logger->info ("Worker ". ($i+1) ." started with pid $wpid");
		push (@workers, $wpid);
	};

	sleep 10;

	while (scalar @workers) {
		for my $proc (0 .. scalar(@workers)-1) {
			my $line = Forks::Super::read_stdout($workers[$proc]);
			next if ! $line;
			if ( $line =~ /___END___/ ) {
				$logger->info("Worker with pid $workers[$proc] terminated succesfully");
				splice (@workers,$proc,1);
				last;
			}
			if ( $line !~ /^#(.+)#$/ ) {
				sleep 1;
				$line .= Forks::Super::read_stdout($workers[$proc]);
				die "Misformed line" if ( $line !~ /^#(.+)#$/ );
			}
			my @result = split (',', $1);
			my $rmod=shift (@result);
			my $rpath=join(',',@result);
			$depindex->addDep(module=>$rmod, path=>$rpath);
		}
	}
	
	$logger->info ("Deleting obsolete entries");
	### Deleting obsolete file entries
	foreach my $m (keys %depdoc) {
		$depindex->delTerm(field => "path",term => $m);
	}
	
	### Deleting obsolete module entries
	foreach my $m (keys %depmod) {
		$depindex->delTerm(field => "module",term => $m);
	}
	
	$logger->info ("Optimizing the index");
	$depindex->setIndexRev(rev => $svnrev);
	$depindex->commit();
	$depindex->optimize();
	$logger->info ("Finished analyzing dependencies");
}
1;
